package com.fi.common.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.ClientBuilderConfiguration;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.fi.common.domain.IRecognAccessInfo;
import com.fi.common.domain.vo.*;
import com.fi.common.service.OssService;
import com.fi.recogn.dto.RecognReq;
import com.fi.recogn.util.FileTypeUtil;
import lombok.Cleanup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

import static com.fi.recogn.util.FileConvertUtil.*;
import static com.fi.recogn.util.FileTypeUtil.*;


@Slf4j
@Service
@SuppressWarnings("all")
public class OssServiceImpl implements OssService, DisposableBean {

    private OSS ossClient;
    private final OsccConfig osccConfig;

    public OssServiceImpl(OsccConfig osccConfig) {
        ClientBuilderConfiguration configuration = new ClientBuilderConfiguration();
        configuration.setConnectionRequestTimeout(50000);
        configuration.setMaxConnections(1024 * 4);
        this.osccConfig = osccConfig;
        try {
            this.ossClient = new OSSClientBuilder().build(osccConfig.getEndpoint(), osccConfig.getAccessKeyId(), osccConfig.getAccessKeySecret(), configuration);
            if (ossClient != null) {
                log.info("======OSSClient 连接成功======");
            } else {
                log.error("======OSSClient 连接失败=====");
            }
        } catch (OSSException e) {
            log.error("OSSClient 连接失败，OSSException:"  + e.getMessage());
        }
    }

    @Override
    public void destroy() throws Exception {
        ossClient.shutdown();
    }

    @Override
    public boolean isExist(String path) {
        try {
            // 判断文件是否存在。filePath文件路径
            return ossClient.doesObjectExist(osccConfig.getBucket(), path);
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public String upload(String fileName, InputStream input) {
        try {
            String filePath = osccConfig.getPath() + fileName;
            PutObjectRequest putObjectRequest = new PutObjectRequest(osccConfig.getBucket(), filePath, input);
            ossClient.putObject(putObjectRequest);
            return filePath;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                input.close();
            } catch (IOException e) {
                log.error("input close error {}", e);
            }
        }
        return null;
    }

    @Override
    @SneakyThrows
    public long getFileSize(String accessoryUrl) {
        if(ObjUtil.isEmpty(accessoryUrl)){
            return 0L;
        }
        if (!isExist(accessoryUrl)) {
            throw new FileNotFoundException("文件不存在~");
        }
        // 获取文件的元数据。
        ObjectMetadata metadata = ossClient.getObjectMetadata(osccConfig.getBucket(), accessoryUrl);

        // 获取文件大小。
        return metadata.getContentLength();
    }

    @Override
    @SneakyThrows
    public InputStream getFileInputStream(String accessoryUrl) {
        if(ObjUtil.isEmpty(accessoryUrl)){
            return null;
        }
        //获取fileid对应的阿里云上的文件对象
        if (!isExist(accessoryUrl)) {
            throw new FileNotFoundException("文件不存在~");
        }
        OSSObject ossObject = ossClient.getObject(osccConfig.getBucket(), accessoryUrl);
        return ossObject.getObjectContent();
    }

    @Override
    public FileData getOssFileContent(Long accessId, String ossPath) throws FileNotFoundException {
        OSSObject ossObject = ossClient.getObject(osccConfig.getBucket(), ossPath);
        try (PDDocument document = Loader.loadPDF(ossObject.getObjectContent())) {
            PDFTextStripper stripper = new PDFTextStripper();
            String pdfContent = stripper.getText(document).replaceAll("\\r", "");
            return FileData.builder().id(accessId).pdfUrl(ossPath).content(pdfContent).build();
        } catch (IOException e) {
            log.error("解析文件异常", e);
            throw new FileNotFoundException("OSS解析pdf文件异常");
        }
    }

    // ******************************************************************************************************
    // *************************下面为核心解析：word、excel、html文件解析*****************************************
    // ******************************************************************************************************

    @Override
    public <T extends FileData> T parseCommon(RecognReq req){
        IRecognAccessInfo info = req.getInfo();
        try{
            if(ObjUtil.isNull(info)){
                throw new IllegalArgumentException("公告信息为空，请检查~");
            }
            if(StrUtil.isBlank(info.getRecognAccessFileUrl())){
                throw new IllegalArgumentException("fileUrl不存在，请检查~");
            }
            // 文件后缀名
            String fileExt = StrUtil.subAfter(info.getRecognAccessFileUrl(), ".", true);
            // 文件大小
            long fileSize = this.getFileSize(info.getRecognAccessFileUrl());
            // 文件输入流
            InputStream inputStream = this.getFileInputStream(info.getRecognAccessFileUrl());
            // 文件类型
            FileTypeUtil.FileType fileType = FileTypeUtil.ofFileTypeOrDefaultWord(fileExt);
            // 构造公告文件基本信息
            FileData fd = null;
            switch (fileType) {
                case WORD:
                    fd = new FileDataWord().setIsDoc2Html(req.getIsDoc2Html()).setIsDocx2Html(req.getIsDocx2Html());
                    break;
                case EXCEL:
                    fd = new FileDataExcel();
                    break;
                case HTML:
                    fd = new FileDataHtml();
                    break;
            }
            return (T) fd.setId(info.getRecognAccessId())
                    .setManager(info.getRecognAccessManager())
                    .setFileName(info.getRecognAccessName())
                    .setPdfUrl(info.getRecognAccessPdfUrl())
                    .setFileUrl(info.getRecognAccessFileUrl())
                    .setFileExt(fileExt)
                    .setFileSize(fileSize)
                    .setInputStream(inputStream)
                    .setFileType(fileType);
        } catch (Exception e) {
            throw new RuntimeException("解析文件核心信息异常");
        }
    }

    @Override
    public <T extends FileData> T parseWordFile(FileDataWord data){
        if(isDoc(data.getFileExt())){
            if(data.getIsDoc2Html()){
                try(InputStream is = data.getInputStream();
                    HWPFDocument document = new HWPFDocument(is);){
                    String htmlStr = docToHtml(document);
                    FileDataHtml html = BeanUtil.copyProperties(data, FileDataHtml.class);
                    html.setDocument(Jsoup.parse(htmlStr));
                    return (T)html;
                } catch (Exception e) {
                    if (!handleRtf(data, e)) {
                        log.error("({})-【解析word-doc文件 2 html】异常",data!=null?data.getId():null,e);
                    }
                }
            }else{
                try(InputStream is = data.getInputStream();
                    HWPFDocument document = new HWPFDocument(is);){
                    data.setHwpfDocument(document,true);
                } catch (Exception e) {
                    if (!handleRtf(data, e)) {
                        log.error("({})-【解析word-doc文件】异常", data != null ? data.getId() : null, e);
                    }
                }
            }
        }
        if(isDocx(data.getFileExt())){
            if(data.getIsDocx2Html()){
                try(InputStream is = data.getInputStream();
                    XWPFDocument document = new XWPFDocument(is);){
                    String htmlStr = docxToHtml(document);
                    FileDataHtml html = BeanUtil.copyProperties(data, FileDataHtml.class);
                    html.setDocument(Jsoup.parse(htmlStr));
                    return (T)html;
                } catch (Exception e) {
                    if (!handleRtf(data, e)) {
                        log.error("({})-【解析word-docx文件】异常", data != null ? data.getId() : null, e);
                    }
                }
            }else{
                try(InputStream is = data.getInputStream();
                    XWPFDocument document = new XWPFDocument(is);){
                    data.setXwpfDocument(document);
                } catch (Exception e) {
                    if (!handleRtf(data, e)) {
                        log.error("({})-【解析word-docx文件】异常", data != null ? data.getId() : null, e);
                    }
                }
            }
        }
        return (T)data;
    }

    /**
     * @desc TODO 处理RTF文件转docx文件
     *
     * @param data 文件数据
     * @param e 异常信息
     * @author lujiahong
     * @date 2024/09/02 22:13
     */
    private boolean handleRtf(FileDataWord data, Exception e) {
        // The document is really a RTF file
        if(e.getMessage()!=null && e.getMessage().contains("RTF")){
            try {
                data.setXwpfDocument(rtfConvertDocx(this.getFileInputStream(data.getFileUrl())));
            }catch (Exception ex){
                log.error("({})-【解析RTF文件】异常",data!=null?data.getId():null,ex);
                return false;
            }
            return true;
        }
        return false;
    }

    @Override
    public <T extends FileData> T parseExcelFile(FileDataExcel data){
        try(InputStream is = data.getInputStream();
            Workbook workbook = WorkbookFactory.create(is)){
            data.setWorkbook(workbook);
        } catch (Exception e) {
            log.error("({})-【解析excel文件】异常",data!=null?data.getId():null,e);
        }
        return (T)data;
    }

    @Override
    public <T extends FileData> T parseHtmlFile(FileDataHtml data){
        StringBuilder fileContent = new StringBuilder();
        String charset = isHtml(data.getFileExt())?CharsetUtil.UTF_8:CharsetUtil.GBK;
        // 从文件读取内容为字符串
        try (InputStreamReader reader = new InputStreamReader(data.getInputStream(), charset)) {
            char[] buffer = new char[4096];
            int bytesRead;
            while ((bytesRead = reader.read(buffer)) != -1) {
                fileContent.append(buffer, 0, bytesRead);
            }
            // 使用Jsoup解析HTML内容
            data.setDocument(Jsoup.parse(fileContent.toString()));
        }catch (Exception e) {
            log.error("({})-【解析html文件】异常",data!=null?data.getId():null,e);
        }
        return (T)data;
    }

}
